package com.fan.cachewebview.core.interceptor

import android.text.TextUtils
import android.util.Log
import com.fan.cachewebview.bean.WebResource
import com.fan.cachewebview.core.CacheConfig
import com.fan.cachewebview.utils.AppUtils
import com.fan.cachewebview.utils.DiskLruCache
import okhttp3.Headers
import okio.buffer
import okio.sink
import okio.source
import java.io.File
import java.util.*
import javax.security.auth.Destroyable

/**
 * @description 磁盘缓存实现
 * @date: 2022/2/28 18:23
 * @author: fan
 */
class DiskInterceptor : ResourceInterceptor, Destroyable {

    private val ENTRY_META = 0
    private val ENTRY_BODY = 1
    private val ENTRY_COUNT = 2

    private var mDiskLruCache: DiskLruCache? = null
    private var cacheConfig: CacheConfig? = null

    constructor(cacheConfig: CacheConfig) {
        this.cacheConfig = cacheConfig
    }

    override fun load(chain: Chain): WebResource? {
        var request = chain.getRequest()
        ensureDiskLruCacheCreate()

        var webResource = getFromDiskCache(request!!.key)
        if (webResource != null && isRealMimeTypeCacheable(webResource)) {
//            Log.d("fan123", "disk cache get:" + request.url)
            return webResource
        }
        webResource = chain.process(request)
        if (webResource != null && isRealMimeTypeCacheable(
                webResource
            )
        ) {
            cacheToDisk(request.key, webResource)
        }
        return webResource
    }


    private fun ensureDiskLruCacheCreate() {
        if (mDiskLruCache != null && !mDiskLruCache!!.isClosed) {
            return
        }
        var dir = cacheConfig!!.getCacheDir()
        var version = cacheConfig!!.getVersion()
        var cacheSize = cacheConfig!!.getDiskCacheSize()

        try {
            mDiskLruCache = DiskLruCache.open(File(dir), version, ENTRY_COUNT, cacheSize)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    private fun getFromDiskCache(key: String): WebResource? {

        try {
            if (mDiskLruCache?.isClosed == true) {
                return null
            }

            var snapshot = mDiskLruCache?.get(key)
            if (snapshot != null) {

                // 获取 status
                var entrySource = snapshot.getInputStream(ENTRY_META).source().buffer()
                var responseCode = entrySource.readUtf8LineStrict()
                var responsePhrase = entrySource.readUtf8LineStrict()


                // 获取 headers
                var headerSize = entrySource.readDecimalLong()
                var headers: Map<String, String>?
                var responseHeadersBuilder = Headers.Builder()

                var placeHolder = entrySource.readUtf8LineStrict()
                if (!TextUtils.isEmpty(placeHolder.trim())) {
                    responseHeadersBuilder.add(placeHolder)
                    headerSize--
                }
                for (i in 0 until headerSize) {
                    val line = entrySource.readUtf8LineStrict()
                    if (!TextUtils.isEmpty(line)) {
                        responseHeadersBuilder.add(line)
                    }
                }
                headers = AppUtils.generateHeadersMap(responseHeadersBuilder.build())


                // 获取 body
                var inputStream = snapshot.getInputStream(ENTRY_BODY)
                if (inputStream != null) {
                    var webResource = WebResource()
                    webResource.reasonPhrase = responsePhrase
                    webResource.responseCode = Integer.parseInt(responseCode)
                    webResource.originBytes = AppUtils.streamToBytes(inputStream)
                    webResource.responseHeaders = headers
                    webResource.isModified = false
                    return webResource
                }
                snapshot.close()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }

    private fun isRealMimeTypeCacheable(resource: WebResource?): Boolean {
        if (resource == null) {
            return false
        }
        val headers = resource.responseHeaders
        var contentType: String? = null
        if (headers != null) {
            val uppercaseKey = "Content-Type"
            val lowercaseKey = uppercaseKey.lowercase(Locale.getDefault())
            val contentTypeValue =
                if (headers.containsKey(uppercaseKey)) headers[uppercaseKey] else headers[lowercaseKey]


            if (!TextUtils.isEmpty(contentTypeValue)) {
                val contentTypeArray = contentTypeValue!!.split(";").toTypedArray()
                if (contentTypeArray.isNotEmpty()) {
                    contentType = contentTypeArray[0]
                }
            }
        }
        return contentType != null && !cacheConfig?.getFilter()?.isFilter(contentType)!!
    }

    private fun cacheToDisk(key: String, webResource: WebResource) {
        if (!webResource.isCacheable) {
            return
        }

        if (mDiskLruCache == null) {
            return
        }

        if (mDiskLruCache != null && mDiskLruCache!!.isClosed) {
            return
        }

        try {
            webResource.isCacheByOurseleves = true
            var editor = mDiskLruCache?.edit(key)
            if (editor == null) {
                Log.d("fan", "另一次编辑进行中")
                return
            }
            var metaOutput = editor.newOutputStream(ENTRY_META)

            var sink = metaOutput.sink().buffer()

            // 写入 status
            sink.writeUtf8(webResource.responseCode.toString()).writeByte('\n'.code)
            sink.writeUtf8(webResource.reasonPhrase).writeByte('\n'.code)


            // 写入 headers
            var headers = webResource.responseHeaders
            sink.writeDecimalLong(headers.size.toLong()).writeByte('\n'.code)
            for ((headerKey, headerValue) in headers) {
                sink.writeUtf8(headerKey)
                    .writeUtf8(": ")
                    .writeUtf8(headerValue)
                    .writeByte('\n'.code)
            }
            sink.flush()
            sink.close()


            // 写入 body
            val bodyOutput = editor.newOutputStream(ENTRY_BODY)
            sink = bodyOutput.sink().buffer()
            val originBytes = webResource.originBytes
            if (originBytes != null && originBytes.isNotEmpty()) {
                sink.write(originBytes)
                sink.flush()
                editor.commit()
            }
            sink.close()

        } catch (e: Exception) {
            try {
                mDiskLruCache?.remove(key)
            } catch (e: Exception) {

            }
//            Log.d("fan123", "disk exception:" + e.printStackTrace())
        }


    }

    override fun destroy() {
        if (mDiskLruCache != null && !mDiskLruCache!!.isClosed) {
            try {
                mDiskLruCache?.close()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}